{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5cf403b3-2ce6-4651-adfe-6d9ab66cdcc3",
   "metadata": {},
   "source": [
    "<!--Copyright © ZOMI 适用于[License](https://github.com/Infrasys-AI/AIInfra)版权许可-->\n",
    "\n",
    "# 手把手实现 Sinusoidal 绝对位置编码\n",
    "\n",
    "Author by: lwh"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "efe83ed9-1288-4963-9100-473552c9e043",
   "metadata": {},
   "source": [
    "## Sinusoidal 绝对位置编码原理\n",
    "\n",
    "在 Transformer 模型中，由于缺乏循环结构或卷积结构，模型本身不具备处理序列中位置信息的能力。为了解决这一问题，论文《Attention is All You Need》中引入了 Sinusoidal（正弦函数）位置编码，它将序列中每个位置编码成一个具有特定模式的向量，并加到输入的词向量中，从而注入位置信息。  \n",
    "\n",
    "Sinusoidal 绝对位置编码具有以下几个特点：\n",
    "\n",
    "- 编码方式不依赖训练（即是固定的，不需要学习的）；\n",
    "- 每个位置使用一组不同频率的正余弦函数；\n",
    "- 不同位置之间的编码具有良好的相对位置可区分性；\n",
    "- 编码具有一定的平移不变性，便于模型学习相对位置信息。\n",
    "\n",
    "给定一个位置 `pos` 和一个维度索引 `i`，位置编码向量中第 `i` 个维度的值由如下公式给出：\n",
    "\n",
    "$$\n",
    "PE_{(pos, 2i)} = \\sin\\left( \\frac{pos}{10000^{\\frac{2i}{d_{\\text{model}}}}} \\right)\n",
    "$$\n",
    "\n",
    "$$\n",
    "PE_{(pos, 2i+1)} = \\cos\\left( \\frac{pos}{10000^{\\frac{2i}{d_{\\text{model}}}}} \\right)\n",
    "$$\n",
    "\n",
    "其中：\n",
    "\n",
    "- $pos$：表示序列中当前的位置；\n",
    "- $i$：表示当前维度索引（从 0 开始）；\n",
    "- $d_{\\text{model}}$：表示位置编码的维度大小；\n",
    "- $PE_{(pos, k)}$：表示位置 $pos$ 在第 $k$ 维的编码值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11223f96-0b75-401d-ac12-70aaae46ad47",
   "metadata": {},
   "source": [
    "## Sinusoidal 绝对位置编码实现\n",
    "\n",
    "下面我们来实现一种非常经典的位置编码方式：正余弦位置编码（Sinusoidal Positional Encoding），它被广泛应用于 Transformer 模型中，为没有时序感知能力的结构显式引入位置信息。\n",
    "\n",
    "首先，我们导入 torch 和 math 库，用于张量操作和数学计算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a38400dc-6d59-461b-9c6a-ddd9c239fc34",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import math"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f26a5a1f-03e0-4943-887c-c139437142a1",
   "metadata": {},
   "source": [
    "然后我们定义一个函数 `sinusoidal_pos_encoding()`，这个函数接收两个参数：\n",
    "\n",
    "- max_len：表示序列的最大长度（也就是你输入句子的最大长度）\n",
    "- d_model：编码维度（也就是每个位置要映射成多长的向量）\n",
    "\n",
    "`sinusoidal_pos_encoding()` 输出是一个形状为 (max_len, d_model) 的张量，表示从位置 0 到位置 max_len - 1 的所有位置的编码值。\n",
    "\n",
    "运行过程中，首先函数内部先检查 d_model 是否为偶数，然后构造位置索引和维度索引，计算每个位置在不同频率下的角度值，分别用正弦函数填入偶数维度、余弦函数填入奇数维度。最终返回一个形状为 (max_len, d_model) 的张量，表示每个位置的唯一编码，使模型在没有循环结构的情况下能够捕捉到位置信息。\n",
    "\n",
    "下面是具体实现代码："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "352e9d6c-1311-4e73-8dc1-8265985d6491",
   "metadata": {},
   "outputs": [],
   "source": [
    "def sinusoidal_pos_encoding(seq_len: int, d_model: int) -> torch.Tensor:\n",
    "    if d_model % 2 != 0:\n",
    "        raise ValueError(\"d_model 必须为偶数\")\n",
    "\n",
    "    # 生成位置向量和维度向量\n",
    "    pos = torch.arange(0, seq_len).unsqueeze(1).float()        # shape: (seq_len, 1)\n",
    "    i = torch.arange(0, d_model // 2).float()                  # shape: (d_model/2,)\n",
    "\n",
    "    # 计算频率除数项\n",
    "    denom = torch.pow(10000, 2 * i / d_model)                  # shape: (d_model/2,)\n",
    "    angle = pos / denom                                        # shape: (seq_len, d_model/2)\n",
    "\n",
    "    # 初始化编码矩阵\n",
    "    pe = torch.zeros(seq_len, d_model)\n",
    "\n",
    "    # 填入 sin 和 cos\n",
    "    pe[:, 0::2] = torch.sin(angle)  # 偶数维度\n",
    "    pe[:, 1::2] = torch.cos(angle)  # 奇数维度\n",
    "\n",
    "    return pe"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ebab260-9dc5-4fbf-a5a8-691175dc641c",
   "metadata": {},
   "source": [
    "在代码的主程序部分，调用 `sinusoidal_pos_encoding(seq_len=3, d_model=4)` 来生成一个长度为 3，每个位置编码为 4 维的正弦位置编码矩阵，并将其保存在变量 pe 中。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06f91b62",
   "metadata": {},
   "source": [
    "## Sinusoidal 结果输出\n",
    "\n",
    "接着通过 `print()` 语句输出编码结果，方便观察每个位置对应的编码值。打印信息前后分别加上“开始输出”和“结束”标记，用于清晰地划分输出区域，下面是代码部分:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9cbad771-d96b-413b-995a-ea06267f77a4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== 开始位置编码输出 ===\n",
      "tensor([[ 0.0000,  1.0000,  0.0000,  1.0000],\n",
      "        [ 0.8415,  0.5403,  0.0100,  0.9999],\n",
      "        [ 0.9093, -0.4161,  0.0200,  0.9998]])\n",
      "=== 结束 ===\n"
     ]
    }
   ],
   "source": [
    "pe = sinusoidal_pos_encoding(seq_len=3, d_model=4)\n",
    "print(\"=== 开始位置编码输出 ===\")\n",
    "print(pe)\n",
    "print(\"=== 结束 ===\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0bcaf166-79e3-4abc-b171-9d796c130ca2",
   "metadata": {},
   "source": [
    "运行该脚本后，程序首先输出提示信息，表明 `sinusoidal_test.py` 已开始执行。\n",
    "\n",
    "随后，函数 `sinusoidal_pos_encoding(seq_len=3, d_model=4)` 返回了一个形状为 `(3, 4)` 的张量，其中每一行表示一个位置的编码，每一列对应一个特定频率下的正弦或余弦值。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
